package arkham.net.messages;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

import java.util.List;
import java.util.ArrayList;

import arkham.net.NetworkUtils;
import arkham.util.AHLogger;
import arkham.util.Pair;

/**
 * The command message allows the server to issue commands to clients, and
 * the clients to respond to the server. Multiple commands can be issued
 * in one message. 
 * @author ctheng
 *
 */
public class CommandMessage extends Message
{
	/**
	 * The active player is the player responsible for responding to 
	 * a command from the server.  
	 */
	private int activePlayer;
	//private List<Pair<String, String>> commands;
	private List<Command> commands;
	
	/**
	 * Creates a new command message specifying all players
	 */
	public CommandMessage()
	{
		this(-1);
	}
	
	/**
	 * Creates a command message specifying an active player
	 * @param activePlayer the active player
	 */
	public CommandMessage(int activePlayer)
	{
		this.setActivePlayer(activePlayer);
		commands = new ArrayList<Command>();
	}
	
	/**
	 * Creates a command message with one command specified with
	 * no active player specified 
	 * @param command the command to issue
	 * @param details the details of the command
	 */
	public CommandMessage(String command, String details)
	{
		this(-1, command, details);
	}
	
	/**
	 * Creates a command message with one command specified
	 * @param activePlayer the active player 
	 * @param command the command to issue
	 * @param details the details of the command
	 */
	public CommandMessage(int activePlayer, String command, String details)
	{
		this(activePlayer);
		addCommand(command, details);
	}

	/**
	 * Gets the list of commands
	 * @return the list of commands
	 */
	public List<Command> getCommands()
	{
		return commands;
	}
	
	/**
	 * Adds a new command to the list of commands, using the default
	 * active player.
	 * @param command the command
	 * @param details the details of the command
	 */
	public void addCommand(String command, String details)
	{
		commands.add(new Command(command, details, activePlayer));
	}
	
	/**
	 * Adds a new command to the list of commands
	 * @param command the command
	 * @param details the details of the command
	 * @param player the player this command refers to 
	 */
	public void addCommand(String command, String details, int player)
	{
		commands.add(new Command(command, details, player));
	}
	
	/**
	 * Adds a list of commands to the original list of commands, using the 
	 * default active player
	 * @param commandList the command list to add
	 */
	public void addCommands(List<Pair<String, String>> commandList)
	{
		for (Pair<String, String> pair : commandList)
		{
			addCommand(pair.getA(), pair.getB(), activePlayer);
		}
	}
	
	/**
	 * Adds a list of commands to the original list of commands
	 * @param commandList the command list to add
	 */
	public void addCommands(List<Pair<String, String>> commandList, int player)
	{
		for (Pair<String, String> pair : commandList)
		{
			addCommand(pair.getA(), pair.getB(), player);
		}
	}
	
	/**
	 * Gets the number of commands
	 * @return the number of commands
	 */
	public int getNumCommands()
	{
		return commands.size();
	}
	
	/**
	 * Get the first command
	 * @return the first command
	 */
	public Command getFirstCommand()
	{
		if (getNumCommands() > 0)
		{
			return commands.get(0);
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * Removes the first command from the list and returns it
	 * @return the first command
	 */
	public Command removeFirstCommand()
	{
		if (getNumCommands() > 0)
		{
			return commands.remove(0);
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * @param activePlayer the activePlayer to set
	 */
	public void setActivePlayer(int activePlayer) {
		this.activePlayer = activePlayer;
	}

	/**
	 * @return the activePlayer
	 */
	public int getActivePlayer() {
		return activePlayer;
	}
	
	/**
	 * Checks if the server has indicated that all players are active
	 * @return true if all players are active.
	 */
	public boolean areAllPlayersActive()
	{
		return activePlayer == -1;
	}
	
	@Override
	public boolean send(Socket sock)
	{
		if (getNumCommands() == 0)
		{
			AHLogger.warning("CommandMessage", "Sending command message with no commands");
		}
		return super.send(sock);
	}

	@Override
	public byte[] toByteArray() {
		// format: numCommands | activePlayer \0 [command \0 details \0]+
		/*
		length = 8;
		for (int i = 0; i < commands.size(); i++)
		{
			Pair<String, String> pair = commands.get(i);
			length += pair.getA().length() + pair.getB().length() + 2;
		}
		byte[] b = new byte[length];
		System.arraycopy(NetworkUtils.intToByteArray(commands.size()), 0, b, 0, 4);
		System.arraycopy(NetworkUtils.intToByteArray(activePlayer), 0, b, 4, 4);		
		int index = 8;
		for (int i = 0; i < commands.size(); i++)
		{
			Pair<String, String> pair = commands.get(i);
			System.arraycopy(pair.getA().getBytes(), 0, b, index, pair.getA().length());
			index += pair.getA().length();
			b[index++] = 0;
			System.arraycopy(pair.getB().getBytes(), 0, b, index, pair.getB().length());
			index += pair.getB().length();
			b[index++] = 0;
		}
		*/
		length = 8;
		for (int i = 0; i < commands.size(); i++)
		{
			Command command = commands.get(i);
			length += command.cmd.length() + command.details.length() + 6;
		}
		byte[] b = new byte[length];
		System.arraycopy(NetworkUtils.intToByteArray(commands.size()), 0, b, 0, 4);
		System.arraycopy(NetworkUtils.intToByteArray(activePlayer), 0, b, 4, 4);		
		int index = 8;
		for (int i = 0; i < commands.size(); i++)
		{
			Command command = commands.get(i);
			System.arraycopy(NetworkUtils.intToByteArray(command.player), 0, b, index, 4);
			index += 4;
			System.arraycopy(command.cmd.getBytes(), 0, b, index, command.cmd.length());
			index += command.cmd.length();
			b[index++] = 0;
			System.arraycopy(command.details.getBytes(), 0, b, index, command.details.length());
			index += command.details.length();
			b[index++] = 0;
		}
		
		return b;
		
	}

	@Override
	public void fromByteArray(byte[] b) throws IOException
	{
		ByteArrayInputStream bais = new ByteArrayInputStream(b);
		byte[] ibuf = new byte[4];
		bais.read(ibuf);
		int numCommands = NetworkUtils.byteArrayToInt(ibuf);
		byte[] apbuf = new byte[4];
		bais.read(apbuf);
		activePlayer = NetworkUtils.byteArrayToInt(apbuf);
		InputStreamReader isr = new InputStreamReader(bais);
		StringBuffer sb1 = new StringBuffer();
		StringBuffer sb2 = new StringBuffer();
		
		char q;
		for (int i = 0; i < numCommands; i++)
		{
			sb1.delete(0, sb1.length());
			sb2.delete(0, sb2.length());
			byte[] val = new byte[4];
			bais.read(val);
			int player = NetworkUtils.byteArrayToInt(val);
			while ((q = (char)bais.read()) != '\0')
			{
				sb1.append(q);
			}
			while ((q = (char)bais.read()) != '\0')
			{
				sb2.append(q);
			}
			commands.add(new Command(sb1.toString(), sb2.toString(), player));
		}
	}

	@Override
	public int getId() 
	{
		return MessageType.COMMAND_MESSAGE.id();
	}
	
	@Override
	public String toString()
	{
		return commands.toString();
	}
	
	public static class Command
	{
		public String cmd, details;
		public int player;
		
		public Command(String cmd, String details)
		{
			this(cmd, details, -1);
		}
		
		public Command(String cmd, String details, int activePlayer)
		{
			this.cmd = cmd;
			this.details = details;
			player = activePlayer;
		}
		
		public String toString()
		{
			return "[(" + player + ")" + cmd + "," + details + "]";
		}
	}

}
